/**
 * 
 */
package com.google.code.cisx.gwt.client.event;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.google.code.cisx.gwt.client.session.EventType;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;

/**
 * Manages Events, Listeners and Event dispatches to appropriate listeners.
 * 
 * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
 * @since 1.0
 * @version $Id: EventManager.java 266 2009-01-21 06:38:36Z rahul.thakur.xdev $
 * @see <a href='http://groups.google.com/group/Google-Web-Toolkit/browse_thread/thread/6635205f5ce8208a/2cffc8ca1d81471f?lnk=gst&q=Event+manager#2cffc8ca1d81471f'>Ev
 *      e n t Manager</a>
 */
public class EventManager {
  /**
   * Singleton {@link EventManager} instance.
   */
  private static final EventManager instance = new EventManager();

  /**
   * Collection of {@link TypedEventListener}s registered for different <em>types</em> of {@link TypedEvent}s.
   */
  private static final Map<EventType, List<TypedEventListener<TypedEvent>>> listeners = new HashMap<EventType, List<TypedEventListener<TypedEvent>>>();

  /**
   * Returns a singleton instance of {@link EventManager}.
   * 
   * @return
   */
  public static EventManager getInstance() {
    return instance;
  }

  /**
   * Adds a Registered Listener.
   * 
   * @param <T>
   *          extension of {@link TypedEventListener}
   * @param type
   *          type of {@link TypedEvent}.
   * @param listener
   *          {@link TypedEventListener} to add.
   */
  @SuppressWarnings("unchecked")
  public <E extends TypedEvent, T extends TypedEventListener<E>> void addEventListener(EventType type, T listener) {
    List<TypedEventListener<TypedEvent>> typedListeners = null;
    if (!listeners.containsKey(type)) {
      listeners.put(type, new EventListenersCollection<TypedEventListener<TypedEvent>>());

      GWT.log("Added listener for event type: " + type.asString(), null);
    }
    typedListeners = listeners.get(type);
    typedListeners.add((TypedEventListener<TypedEvent>) listener);
  }

  /**
   * Removes a Registered listener.
   * 
   * @param <T>
   *          extension of {@link TypedEventListener}
   * @param type
   *          type of {@link TypedEvent}.
   * @param listener
   *          {@link TypedEventListener} to remove.
   */
  public <E extends TypedEvent, T extends TypedEventListener<E>> void removeEventListener(EventType type, T listener) {
    List<TypedEventListener<TypedEvent>> typedListeners = null;
    if (listeners.containsKey(type)) {
      typedListeners = listeners.get(type);
      typedListeners.remove(listener);
    }
  }

  /**
   * Dispatches Event notification to {@link TypedEventListener}s registered for that particular type of Event.
   * 
   * @param <E>
   *          extension of {@link TypedEvent}.
   * @param event
   *          event to handle
   */
  public <E extends TypedEvent> void dispatchEvent(final E event) {
    if (listeners.containsKey(event.getType())) {
      final EventListenersCollection<TypedEventListener<TypedEvent>> typeListeners = (EventListenersCollection<TypedEventListener<TypedEvent>>) listeners
          .get(event.getType());
      DeferredCommand.addCommand(new Command() {
        public void execute() {
          GWT.log("Dispatching event: " + event.toString() + ", to " + typeListeners.size() + " listeners.", null);
          typeListeners.onEvent(event);
        }
      });

    }
  }

  /**
   * Collection of Event Listeners registered for a <em>type</em> of event.
   * 
   * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
   * @since 1.0
   * @param <T>
   *          {@link TypedEventListener}
   */
  static class EventListenersCollection<T extends TypedEventListener<TypedEvent>> extends ArrayList<T> {
    private static final long serialVersionUID = 3425680778337656685L;

    public void onEvent(TypedEvent event) {
      for (Iterator<T> it = iterator(); it.hasNext();) {
        T listener = it.next();
        listener.onEvent(event);
      }
    }
  }

}
